package fr.alma.trajectory.rendering;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.List;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

import fr.alma.trajectory.commons.SpaceObject;
import fr.alma.trajectory.commons.SpacePoint;

/**
 * Classe générant le rendu OpenGL 
 * @author M2 ALMA 2011 Multimedia
 *
 */
public class Renderer implements GLEventListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
	private GLU glu = new GLU();

	private GL2 gl;

	// Keyboard input
    private boolean[] keys;
    
    //Mouse input
    int prevMouseX;
    int prevMouseY;
    float diffX;
    float diffY;
    boolean mouseRButtonDown;
    boolean mouseRButtonMove;
    boolean mouseLButtonDown;
    boolean mouseLButtonMove;
    
    // Camera variables
    private Camera camera;
	
	// la liste des triangles calculée et envoyée à l'écran
	private List<SpaceObject> objects;
	
	// le squelette
	private GLSquelette glSquelette;
	 
    public Renderer(List<SpaceObject> objects){
		this();
		this.objects = objects;
	}
	
	public void setListObjects(List<SpaceObject> objects) {
		this.objects = objects;
	}
	
	public void setSquelette(GLSquelette glsquelette){
		this.glSquelette = glsquelette;
	}

	public Renderer() {
    	// boolean array for keyboard input
        keys = new boolean[256];
        
    	// Initialize the user camera        
    	camera = new Camera();
		camera.init();
	}
	
	/*
	 * (non-Javadoc)
	 * @see javax.media.opengl.GLEventListener#display(javax.media.opengl.GLAutoDrawable)
	 */
	public void display(GLAutoDrawable gLDrawable) {
	    update();
	    
        camera.keyboardChecks(keys);	// Responds to keyboard input
        handleMuseInput();
                       
        render(gLDrawable);		// Draw the scene
	}

	private void handleMuseInput() {
		if(mouseLButtonDown){
        	if(mouseLButtonMove){
		    	if(diffX<0){
		    		camera.yawLeft(0.01*(-diffX/50));
		    		camera.look(10);
		    	}
		    	if(diffX>0){
		    		camera.yawRight(0.01*(diffX/50));
		    		camera.look(10);
		    	}
		    	if(diffY<0){
		    		camera.pitchUp(0.01*(-diffY/50));
		    		camera.look(10);
		    	}
		    	if(diffY>0){
		    		camera.pitchDown(0.01*(diffY/50));
		    		camera.look(10);
		    	}

        	}
        }
        if(mouseRButtonDown){
        	if(mouseRButtonMove){
		    	if(diffX<0){
		    		camera.strafeLeft(0.01*(-diffX/10));
		    		camera.look(10);
		    	}
		    	if(diffX>0){
		    		camera.strafeRight(0.01*(diffX/10));
		    		camera.look(10);
		    	}
		    	if(diffY<0){
		    		camera.moveForward(0.05);
		    		camera.look(10);
		    	}
		    	if(diffY>0){
		    		camera.moveForward(-0.05);
		    		camera.look(10);
		    	}
        	}
        }
	}

	private void render(GLAutoDrawable gLDrawable) {
		final GL2 gl = gLDrawable.getGL().getGL2();
		
		reshape(gLDrawable, 0, 0, gLDrawable.getWidth(), gLDrawable.getHeight());
		
        // On efface le buffer de l'écran
        gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
        init(gLDrawable);		
             
        // Ajout de la visu du squelette de cubes 3D
        if(camera.squelettemode){
        	if(glSquelette!=null)
        		glSquelette.render();
        }
        // ajout du cube entournant la lumière choisie, si il y en a
        int lightSelected = camera.getLightSelected();
        if(lightSelected != -1) {
        	gl.glBegin(GL2.GL_QUADS);
        	GLSquelette.drawCubeWithGL(gl, Lights.light_position[lightSelected][0], 
        			Lights.light_position[lightSelected][1],
        			Lights.light_position[lightSelected][2],
        			0.1);
        	gl.glEnd();
        }
        
        if(camera.vueSolide) {
        	gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
        } else {
        	gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE);
        }
                       
        for (int k= 0 ; k< objects.size()-1;k++){
          	//ici traitement des faces
        	displayObjects(objects.get(k),objects.get(k+1),gl);
        	displayObject(objects.get(k),gl);
        }
     
	}

	private void displayObjects(SpaceObject obj1 ,SpaceObject obj2, GL2 gl) {
		gl.glBegin(GL2.GL_TRIANGLE_STRIP);
		gl.glColor3f(1.0f,1.0f, 1.0f);
			for(int k = 0; k<obj1.getSpacePoints().size();k++){
				gl.glVertex3f(	(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(k)).x,
								(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(k)).y,
								(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(k)).z);
				gl.glVertex3f(	(float)(((List<SpacePoint>) obj2.getSpacePoints()).get(k)).x,
								(float)(((List<SpacePoint>) obj2.getSpacePoints()).get(k)).y,
								(float)(((List<SpacePoint>) obj2.getSpacePoints()).get(k)).z);
			}
			gl.glVertex3f(	(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(0)).x,
							(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(0)).y,
							(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(0)).z);
			gl.glVertex3f(	(float)(((List<SpacePoint>) obj2.getSpacePoints()).get(0)).x,
							(float)(((List<SpacePoint>) obj2.getSpacePoints()).get(0)).y,
							(float)(((List<SpacePoint>) obj2.getSpacePoints()).get(0)).z);
		gl.glEnd();
	}
	
	private void displayObject(SpaceObject obj1, GL2 gl) {
		//
		
		
		gl.glBegin(GL2.GL_POLYGON);
		gl.glColor3f(0.5f,0.5f, 0.5f);
			for(int k = 0; k<obj1.getSpacePoints().size();k++){
				
				gl.glVertex3f(	(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(k)).x,
								(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(k)).y,
								(float)(((List<SpacePoint>) obj1.getSpacePoints()).get(k)).z);
				
			}
		gl.glEnd();
	}

	private void update() {
		//METTRE A JOUR LA POSITION (tourner sur l'axe Y)
	}

    public void init(GLAutoDrawable drawable) 
    {
    	gl = drawable.getGL().getGL2();
        glu = new GLU();
        gl.setSwapInterval(0); 		// Refreshes screen at 60fps   
       
//        for(int i = 0 ; i < Lights.codes.length; i++) {
//        	gl.glEnable(Lights.codes[i]);
//        }
//        
//        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, Lights.light_ambient, 0);
//        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, Lights.light_specular, 0);
//        //gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_EMISSION, Lights.light_emmission, 0);
//        gl.glLightfv(Lights.codes[0], GL2.GL_DIFFUSE, Lights.light_diffuse[1], 0);
//    	gl.glLightfv(Lights.codes[0], GL2.GL_POSITION, Lights.light_position[1], 0);
                                
        /*						*/
        /*  lumière de la scène */
        /*						*/
        
        float light_ambient[] = { 0f, 0f, 0f, 1f };
        float light_diffuse[] = { 1f, 1f, 1f, 1f };
        float light_specular[] = { 1f, 1f, 1f, 1f };
        float light_emmission[] = { 0f, 0f, 0f, 1f };
        
        /* light_position is NOT default value */
        float light_position[] = { 10.0f, 20.0f, 6.0f, 1.0f };

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, light_ambient, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, light_diffuse, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, light_specular, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, light_position, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_EMISSION, light_emmission, 0);
       
        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(GL2.GL_LIGHT0);
        gl.glEnable(GL2.GL_COLOR_MATERIAL);
        gl.glColorMaterial(GL2.GL_FRONT,GL2.GL_AMBIENT_AND_DIFFUSE);
        gl.glMateriali(GL2.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 100);
        
        gl.glShadeModel(GL2.GL_SMOOTH);
        gl.glClearColor(0.0f,0.0f,0.0f,0.0f);
        gl.glClearDepth(1.0f);												// Depth Buffer Setup
    	gl.glEnable(GL.GL_DEPTH_TEST);										// Enables Depth Testing
    	gl.glDepthFunc(GL.GL_LEQUAL);										// The Type Of Depth Test To Do
    	gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);		// Really Nice Perspective Calculations
    	
    }
    
    public void lights(GLAutoDrawable drawable) {
    	
    	gl = drawable.getGL().getGL2();
    	    	       
        for(int i = 0; i < Lights.activated.length; i++) {
        	if(Lights.activated[i]) {
        		 gl.glLightfv(Lights.codes[i], GL2.GL_DIFFUSE, Lights.light_diffuse[i], 0);
        		 gl.glLightfv(Lights.codes[i], GL2.GL_POSITION, Lights.light_position[i], 0);
        	}
        }        
    }

	public void reshape(GLAutoDrawable gLDrawable, int x, int y, int width, int height){
		final GL2 gl = gLDrawable.getGL().getGL2();		

		if (height <= 0){ 
			height = 1;
		}

		gl.glViewport(0, 0, width, height);
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(45f, (float)width/(float)height, 0.01f, 200f);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		
		glu.gluLookAt(camera.getXPos(), camera.getYPos() , camera.getZPos(), 
        		camera.getXLPos(), camera.getYLPos(), camera.getZLPos(), 0.0, 1.0, 0.0);		
	}
	
	public GL2 getGl() {
		return gl;
	}

	public void setGl(GL2 gl) {
		this.gl = gl;
	}


	public void dispose(GLAutoDrawable arg0){
	}

	@Override
	public void keyPressed(KeyEvent key) 
    {
    	try
    	{
        char i = key.getKeyChar();
        keys[(int)i] = true;
    	}
    	catch(Exception e){}; 
    	camera.doKeyPressed(key);
    }
    
	@Override
    public void keyReleased(KeyEvent key) 
    {
    	try
    	{
        char i = key.getKeyChar();
        keys[(int)i] = false;
    	}
    	catch(Exception e){};
    }

	@Override
	public void mousePressed(MouseEvent e) 
	{
		prevMouseX = e.getX();
		prevMouseY = e.getY();
		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
			mouseLButtonDown = true;
		}
		if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
			mouseRButtonDown = true;
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) 
	{
		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
		      mouseLButtonDown = false;
		      mouseLButtonMove = false;
		      prevMouseX = e.getX();
		      prevMouseY = e.getY();
		  }
		if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
		      mouseRButtonDown = false;
		      mouseRButtonMove = false;
		      prevMouseX = e.getX();
		      prevMouseY = e.getY();
		  }
		
	}

	@Override
	public void mouseDragged(MouseEvent e) 
	{
		
		int x = e.getX();
	    int y = e.getY();          
	    if (mouseLButtonDown) {
	    	mouseLButtonMove = true;
	    	diffX = (float)(x - prevMouseX);
	    	diffY = (float)(y - prevMouseY);
	    	
	    }else if (mouseRButtonDown){
	    	mouseRButtonMove = true;
	    	diffX = (float)(x - prevMouseX);
	    	diffY = (float)(y - prevMouseY);
	    }else {
	    	prevMouseX = x;
	    	prevMouseY = y;
	   }
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) 
	{
		int notches = e.getWheelRotation();
	       if (notches < 0) {
	    	   camera.moveForward(0.5);
	    	   camera.look(10);
	       } else {
	    	   camera.moveForward(-0.5);
	           camera.look(10);
	       }
	}

	@Override
	public void mouseMoved(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseClicked(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
		// TODO Auto-generated method stub
		
	}    
	
}
